package pos.Driving.Train;

import groovy.lang.GroovyClassLoader;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import javax.media.j3d.*;
import javax.vecmath.Vector3d;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import pos.Driving.Camera.Camera;
import pos.Driving.Dashboard.IDashboard;
import pos.Driving.Dashboard.Image.Dashboard;
import pos.Driving.Dashboard.SpeedDesc;
import pos.Driving.DrivingController;
import pos.Driving.Path.*;
import pos.IModeController;
import pos.utils.Invokeable;
import pos.utils.PathRes;
import pos.utils.SFXClip;

/**
 *
 * @author plus
 */
public class Train implements Invokeable {

    public final static int MODE_OFF = 0,  MODE_ATPFORWARD = 1,  MODE_ATPBACKWARD = 2,  MODE_ATO = 3,  MODE_WM = 4;
    public final static int STATUE_READY = 0,  STATUE_OPERATING = 1,  STATUE_REACHSTATION = 2,
            STATUE_DOOROPEN = 3,  STATUE_DRIVEROUTSIDE = 4,  STATUE_EB = 5;
    public final static int ALLOW_LEFT_DOOR_OPEN = 1,  ALLOW_RIGHT_DOOR_OPEN = 2,  ALLOW_NONE_DOOR_OPEN = 0;

    private PosPoint trainPos = new PosPoint();
    private double speed = 0,  pow = 0;
    private int stationNum = 0;
    private boolean isReversed;
    private int currentMode, targetMode;            //操作模式

    private int currentStatue;          //状态

    private int keyCode;
    private boolean isATPEnable,  isDriverOutside, isModeChangingCleared;
    private int doorLock;
    private static IModeController myController;
    private IDashboard myDashboard;
    protected TrainDesc MyDesc;
    protected ATP apt;
    //protected Segment MySegment;
    protected ArrayList<Carriage> Carriages;
    protected DriverRoom MyDriverRoom;
    protected HashMap <String, SFXClip> SFXMap;
    BranchGroup TrainRoot = new BranchGroup();
    
    private Door doorTemp;
    private ITrainDefiner trainDefiner;                        //车辆行为定义器
    
    public Train(String descXMLPathKey, IModeController myController, int pathID) {
        Train.myController = myController;
        this.isReversed = false;
        this.trainPos = new PosPoint();
        this.apt = new ATP(this, 0);
        isATPEnable = true;
        this.isModeChangingCleared=true;
        doorLock = Train.ALLOW_NONE_DOOR_OPEN;

        myDashboard = new Dashboard();
        SFXMap = new HashMap<String, SFXClip>();
        ((DrivingController) myController).addCPChild((Node)myDashboard);

        TrainRoot.setCapability(BranchGroup.ALLOW_DETACH);
        
        DocumentBuilderFactory factory;
        DocumentBuilder builder;
        Document document;
        factory = DocumentBuilderFactory.newInstance();
        try{
            builder = factory.newDocumentBuilder();
            document = builder.parse(
                    new File(PathRes.getPath(descXMLPathKey)));
            this.MyDesc=new TrainDesc(document);
            
            initTrain(document);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    private void initTrain(Document doc){  //初始化车辆各部件
        XPathFactory xpfactory = XPathFactory.newInstance();
        XPath xpath = xpfactory.newXPath();
        if (doorTemp == null){
            try{
                doorTemp=new Door(new Transform3D(),
                        xpath.evaluate("/train/door/doorAModel", doc),
                        xpath.evaluate("/train/door/doorBModel", doc),
                        xpath.evaluate("/train/door/doorLightModel", doc),
                        this);
            }catch(Exception e){
                e.printStackTrace();
            }
        }

        Carriages=new ArrayList<Carriage>();
        try{
            int carriages = Integer.parseInt(
                xpath.evaluate("count(/train/carriages/carriage)", doc).trim());
            for (int iCarriage=1; iCarriage<=carriages; iCarriage++){
                Carriages.add(new Carriage(this,doc,iCarriage,
                   getDoors(iCarriage,doc, false), getDoors(iCarriage,doc,true)));
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        try{
            SFXMap.put("DoorClose", 
                    new SFXClip(xpath.evaluate("/train/sfx/doorClose", doc)));
            SFXMap.put("DoorOpen",
                    new SFXClip(xpath.evaluate("/train/sfx/doorOpen", doc)));
        }catch(Exception e){
            e.printStackTrace();
        }
        
        try{
            String definerClassName =
                    xpath.evaluate("/train/script", doc);
            trainDefiner =(ITrainDefiner)
                    new GroovyClassLoader().parseClass(
                    new File(PathRes.getPath(definerClassName)))
                    .newInstance();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    //protected abstract double speedCalc(double speed, double power, double step); //车辆速度计算

    public PosPoint getTrainPos() {
        return getTrainPos(false);
    }

    public PosPoint getTrainPos(boolean isAbsPos) {
        PosPoint res = new PosPoint(trainPos);
        if (this.isReversed && !isAbsPos) {
            res.d += MyDesc.Length;
        }
        return res;
    }

    public void setIsReversed(boolean isReversed) {
        this.isReversed = isReversed;
    }

    public void setTrainInitialPos(double D, int pathID) {
        this.trainPos.d = D;
        this.trainPos.path_ID = pathID;
        for (Carriage carriage: Carriages){
            carriage.syncPathID();
        }
    }

    public void setTrainPath(int pathID) {
        this.trainPos.path_ID = pathID;
    }

    public void setATPSpeedLimit(double speedLimit) {
        this.apt.setSpeedSignal(speedLimit);
    }

    public int getCurrentMode() {
        return currentMode;
    }

    public int getCurrentStatue() {
        return currentStatue;
    }

    public void setCurrentMode(int currentMode) {
        this.currentMode = currentMode;
    }

    public void setCurrentStatue(int currentStatue) {
        this.currentStatue = currentStatue;
    }

    public double getSpeed() {
        return speed;
    }

    
    public boolean isIsReversed() {
        return isReversed;
    }

    public int getDoorLock() {
        return doorLock;
    }

    public BranchGroup getTrainGroup() {
        return this.TrainRoot;
    }

    /**
     * 处理键盘事件
     * @param evt 待处理的键盘事件
     */
    public void processKeyEvent(KeyEvent evt) {
        if (evt.getID() == KeyEvent.KEY_RELEASED) {
            keyCode = 0;
            switch (evt.getKeyCode()) {
                case KeyEvent.VK_ESCAPE:
                    myController.doEvent("PauseMsg", null);
                    break;
                case KeyEvent.VK_LEFT:
                    pow = -1;
                    break;
                case KeyEvent.VK_RIGHT:
                    pow = 0;
                    break;
                case KeyEvent.VK_F1:
                    myController.doEvent("ChangeView@4", null);
                    break;
                case KeyEvent.VK_F2:
                    myController.doEvent("ChangeView@0", null);
                    break;
                case KeyEvent.VK_F3:
                    myController.doEvent("ChangeView@1", null);
                    break;
                case KeyEvent.VK_F4:
                    myController.doEvent("ChangeView@2", null);
                    break;

                case KeyEvent.VK_F5:
                    myController.doEvent("ChangeView@3", null);
                    break;

                case KeyEvent.VK_Q:
                    openDoors();
                    break;
                case KeyEvent.VK_W:
                    closeDoors();
                    break;
                case KeyEvent.VK_1:
                    this.changeMode(Train.MODE_ATPFORWARD);
                    break;
                case KeyEvent.VK_2:
                    this.changeMode(Train.MODE_ATPBACKWARD);
                    break;
                case KeyEvent.VK_ENTER:
                    if (!this.isDriverOutside) {
                        this.driverOff();
                    } else {
                        this.driverOn();
                    }
            }
        } else if (evt.getID() == KeyEvent.KEY_PRESSED) {
            switch (evt.getKeyCode()) {
                case KeyEvent.VK_UP:
                    if (keyCode != 3) {
                        keyCode = 1;
                    }
                    break;
                case KeyEvent.VK_DOWN:
                    if (keyCode != 3) {
                        keyCode = 2;
                    }
                    break;
                case KeyEvent.VK_SPACE:
                    this.EB();
            }
        }
    }

    public IDashboard getDashboard() {
        return myDashboard;
    }

    public void invoke(double Step) {
        //System.out.println(currentStatue);
        if (currentStatue == Train.STATUE_DOOROPEN) {
            for (Carriage carriage : Carriages) {
                carriage.invoke(Step);
            }

        } else if (currentStatue == Train.STATUE_OPERATING ) {
            operating(Step);

        } else if (currentStatue == Train.STATUE_REACHSTATION){
            if (!isDriverOutside){
                operating(Step);
                if (speed>0.01)
                    currentStatue=STATUE_OPERATING;
            }

        } else if (currentStatue == Train.STATUE_EB) {
            if (speed > 0) {
                pow = -2;
                operating(Step);
            } else {
                pow = 0;
                speed = 0;
                if(!this.isModeChangingCleared){
                    isModeChangingCleared=true;
                    currentMode=targetMode;
                }
                currentStatue = Train.STATUE_OPERATING;
            }
        }
        setPower(Step);

        myDashboard.setDest(getTrainPos().d);
        ((Invokeable)myDashboard).invoke(Step);
    }

    private void setPower(double step) {
        switch (keyCode) {
            case 1:
                if (pow < 1) {
                    pow += step * 0.5;
                } else {
                    pow = 1;
                }
                break;
            case 2:
                if (pow > -1) {
                    pow -= step * 0.5;
                } else {
                    pow = -1;
                }
                break;

        }
    //keyCode=0;
    }

    private void operating(double step) {
        // myController.doEvent("SetCameraStatue", Camera.STATUE_DRIVER);
        // myController.doEvent("SetCameraFollowingType", Camera.FOLLOW_CARRIAGE);

        double speedStep = 0;
        if (getCurrentMode() != Train.MODE_OFF) {
            speed = trainDefiner.speedCalc(speed, pow, step);                                      //更新速度
           /* trainDefiner.setProperty("speed", speed);
            trainDefiner.setProperty("power", pow);
            trainDefiner.setProperty("step", step);
            trainDefiner.invokeMethod("calcSpeed", null);
            speed=(Double)trainDefiner.getProperty("result");*/
        }
        if (isATPEnable) {
            this.apt.checkOverSpeed(speed, step);
        }
        speedStep = speed / 3.6 * step;
        // System.err.println(speedStep);
        if (!this.isReversed) {
            speedStep = -speedStep;
        }
        if (this.getCurrentMode() == Train.MODE_ATPBACKWARD) {
            speedStep = -speedStep;
        }
        this.trainPos.d += speedStep;

        if (Math.abs(speedStep) > 0){ //报告车辆位置
            trainAction(this);
        }
        for (Carriage carriage : Carriages) {
            carriage.RefreshPosition();
        }
        myDashboard.setCurrentSpeed(new SpeedDesc(
                speed, this.apt.getSpeedLimit(), this.apt.getSpeedSignal()));
        myDashboard.setPower(pow);
        //fuck//
        /*if (this.getTrainPos().d<20){
            speed=0;
            pow=0;
            this.isReversed=!this.isReversed;
        }*/
    }

    public void setSpeed(double speed) {
        this.speed = speed;
    }

    public void setOnStation(int stationID, int doorLock) {
        if (speed < 0.0001) {
            this.stationNum = stationID;
            this.doorLock = doorLock;
            //System.err.println(doorLock);
            this.currentStatue = Train.STATUE_REACHSTATION;

            if(!this.isModeChangingCleared){                    //clear EB
                    isModeChangingCleared=true;
                    currentMode=targetMode;
            }
        }
    }

    public int getStationNum() {
        return stationNum;
    }

    public void setPow(double pow) {
        keyCode=0;
        this.pow = pow;
    }
    
    public void openDoors() {
        if (currentStatue == Train.STATUE_REACHSTATION) {
            SFXClip sfxDoorClose=SFXMap.get("DoorOpen");
            if (sfxDoorClose!=null){
                sfxDoorClose.stopPlay();
                sfxDoorClose.play();
            }
            if (doorLock == Train.ALLOW_LEFT_DOOR_OPEN) {
                for (Carriage carriage : Carriages) {
                    if (!this.isReversed)
                        carriage.openDoor(false);
                    else
                        carriage.openDoor(true);
                }
            }
            if (doorLock == Train.ALLOW_RIGHT_DOOR_OPEN) {
                for (Carriage carriage : Carriages) {
                    if (!this.isReversed)
                        carriage.openDoor(true);
                    else
                        carriage.openDoor(false);
                }
            }
            currentStatue = Train.STATUE_DOOROPEN;
        }
    }

    public void closeDoors() {
        if (currentStatue == Train.STATUE_DOOROPEN && this.isDriverOutside) {     //Driver must go out to close the door
            SFXClip sfxDoorClose=SFXMap.get("DoorClose");
            if (sfxDoorClose!=null){
                sfxDoorClose.stopPlay();
                sfxDoorClose.play();
            }
            if (doorLock == Train.ALLOW_LEFT_DOOR_OPEN) {
                for (Carriage carriage : Carriages) {
                    if (!this.isReversed)
                        carriage.closeDoor(false);
                    else
                        carriage.closeDoor(true);
                }
            }
            if (doorLock == Train.ALLOW_RIGHT_DOOR_OPEN) {
                for (Carriage carriage : Carriages) {
                    if (!this.isReversed)
                        carriage.closeDoor(true);
                    else
                        carriage.closeDoor(false);
                }
            }
        }
    }

    public void doorsClose() {
        currentStatue = Train.STATUE_DRIVEROUTSIDE;
    }

    public void driverOff() {
        if (currentStatue == Train.STATUE_REACHSTATION ||
                currentStatue == Train.STATUE_DOOROPEN) {
            this.isDriverOutside = true;
            myController.doEvent("SetCameraStatue", Camera.STATUE_DRIVER);
        }
    }

    public void driverOn() {
        if (currentStatue == Train.STATUE_DRIVEROUTSIDE) {
            this.isDriverOutside = false;
            currentStatue = Train.STATUE_READY;
            //currentStatue = Train.STATUE_OPERATING;
            myController.doEvent("SetCameraStatue", Camera.STATUE_FOLLOWING);
        }
    }

    public void EB() {
        if (currentStatue == Train.STATUE_OPERATING) {
            currentStatue = Train.STATUE_EB;
        }
    }

    /*public void setIsSideWay(boolean isSideWay){
    this.trainPos.isSideway=isSideWay;
    }*/
    public PosPoint refreshPosition(PosPoint pos) {
        return (PosPoint) myController.doEvent("RefreshPosition", pos);
    }

    private void changeMode(int newMode){
        if (currentStatue==Train.STATUE_OPERATING){
            if (newMode==currentMode)
                return;
            targetMode=newMode;
            this.isModeChangingCleared=false;
            refreshModeDisplay();
            this.EB();
        }else if (currentStatue==Train.STATUE_EB){
                isModeChangingCleared=false;
                targetMode=newMode;
                refreshModeDisplay();
        }else if (currentStatue==STATUE_READY || currentStatue==STATUE_REACHSTATION){
            currentMode=newMode;
            refreshModeDisplay();
        }
    }
    private void refreshModeDisplay(){
        int mode=currentMode;
        if (!isModeChangingCleared)
            mode = targetMode;
        
        if (mode ==Train.MODE_ATPFORWARD)
              this.myDashboard.setControlMode("ATP手动前进");
        else if (mode  == Train.MODE_ATPBACKWARD)
              this.myDashboard.setControlMode("ATP手动后退");
        
        
        myDashboard.setCurrentSpeed(new SpeedDesc(
                speed, this.apt.getSpeedLimit(), this.apt.getSpeedSignal()));
    }
    private void trainAction(Train train) {
        myController.doEvent("TrainAction", train);
    }
    
    private Door[] getDoors(int carriageID, Document doc,boolean isRight){
        XPathFactory xpfactory = XPathFactory.newInstance();
        XPath xpath = xpfactory.newXPath();
        Door [] res;
       try{
           int doors = Integer.parseInt(
                xpath.evaluate("count(/train/carriages/carriage["+
                    carriageID+"]/doorOffset)", doc).trim());
            res= new Door[doors];
            for (int iDoor=1; iDoor<=doors; iDoor++){
                res[iDoor-1]= new Door(
                        getDoorT3D(
                            Double.parseDouble(
                                xpath.evaluate("/train/carriages/carriage["+
                                carriageID+"]/doorOffset["+iDoor+"]", doc).trim()),
                            isRight),
                        doorTemp,this
                        );
            }
            return res;
       }catch(Exception e){
           e.printStackTrace();
       }
       return null;
    }

    private Transform3D getDoorT3D(double offset, boolean isRight){
        Transform3D t3dV=new Transform3D(), t3dT=new Transform3D();
        if (isRight){
            t3dV.rotY(Math.PI);
            t3dT.set(new Vector3d(offset+1.55,1.13,-1.47));
            t3dT.mul(t3dV);
        }else{
            t3dT.set(new Vector3d(offset,1.13, 1.47));
            //t3dV.mul(t3dT);
        }
        return t3dT;
    }
}
